const Client = require("./net/Client")
const Queue = require("./Queue")
const net = require('net');
const log4js = require("log4js");
const Log = log4js.getLogger("proxy");
const MD5 = require("./md5")

/**
 * 代理类管理着 Client （伪）
 * Client （伪） 就是真实请求的模拟， 一一对应。
 * 启动时连接到 Master
 * 根据收到到命令创建使用和删除 Client （伪）
 * 同时将 Client （伪） 收到的数据返回给请求者。
 */

class Proxy {
    constructor(mip, mport, password, rport, lport, lip){
        this.mip = mip;
        this.mport = mport;
        this.password = MD5(MD5(password));
        this.lport = lport;
        this.rport = rport;
        this.lip = lip;
        this.clients = {};
    }

    async start(){
        await this.connect();
        Log.debug("已连接到 Master " + this.mip + ":" + this.mport)
    }

    /**
     * 创建与 Master 的持久连接
     * @returns {Promise.<void>}
     */
    async connect(){
        return new Promise( (ok, err) =>{
            let client = new Client();
            this.toMaster = client;
            client.connect(this.mport, this.mip, () => {

                Log.debug('连接到 Master: ' + this.mip + ':' + this.mport, " 开始申请端口 "+ this.rport +" ...");

                client.send({
                    cmd : "reg" ,
                    data : {
                        password : this.password,
                        port : this.rport
                    }
                });

                this.waitMaster = {
                    ok : ok,
                    err : err
                }
            });

            client.on('data', (data) => {
                Log.debug("从 Master 收到包, 执行命令: ", data.cmd);
                this.execute(data.cmd, data.data)
            });


            client.on('close', () => {
                Log.debug("Disconnect Master")
                err && err()
                process.exit(-1)
            });

            client.on('error', () => {
                Log.debug("Disconnect Master")
                err && err()
                process.exit(-1)
            });
        })
    }

    execute(cmd, data){
        switch (cmd){
            case "ready" :
                Log.debug("执行命令 ready");
                if( this.waitMaster ){
                    let ok = this.waitMaster.ok;
                    this.waitMaster = undefined;
                    ok && ok();
                }
                break;

            case "data" :{
                Log.debug("执行命令 data" , data.client);
                let client = this.clients[data.client];
                let d = new Buffer(data.origin.data);
                if( client ){
                    client.write(d);
                }else{
                    // 可能是由于影子客户端没有完全创建好
                    // 暂时缓存数据
                    Queue.setData(data.client, d)
                    Log.debug("已缓冲数据", Queue.has(data.client))
                }
                break;
            }

            case "new" : {
                Log.debug("执行命令 new" , data.client);
                let host = this.lip ? this.lip : 'localhost';
                let client = new net.Socket();
                let name = data.client;
                Log.debug('创建同名影子客户端 ' + name);
                client.connect(
                    this.lport,
                    host,
                    () => {
                        Log.debug(name + '客户端连接成功');
                        // 检测是否有缓存的数据，优先发送缓存.
                        let ds = Queue.getAllData(name);
                        Log.debug("检测缓冲数据 ", ds.length )
                        for( let d of ds ){
                            client.write(d);
                        }
                        // 保存同名影子客户端 cc
                        this.clients[name] = client;
                    });

                client.on('data', (data) =>{
                    Log.debug(name + " 收到原始数据")
                    this.toMaster && this.toMaster.send({
                        cmd  : "data",
                        data : {
                            client : name,
                            origin : data
                        }
                    })
                });

                client.on('close', () => {
                    Log.debug(name + " 被目标服务关闭")
                    this.toMaster && this.toMaster.send({
                        cmd  : "delete",
                        data : {
                            client : name
                        }
                    })
                });
                break;
            }

            case "delete" : {
                Log.debug("执行命令 delete" , data.client);
                Log.debug("Master delete " + data.client)
                let client = this.clients[data.client]
                if( client ){
                    client.destroy();
                    delete this.clients[data.client];
                }

                break;
            }

            case "error" :
                Log.error(data)
                if( this.waitMaster ){
                    let err = this.waitMaster.err;
                    this.waitMaster = undefined;
                    err && err();
                    process.exit(-1)
                }
                break;
        }
    }
}


module.exports = Proxy;